package com.whl.mybatis.interceptors;

import com.whl.mybatis.builder.MybatisBuilder;
import com.whl.mybatis.helper.PageHelper;
import com.whl.mybatis.helper.PageInfo;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

@Intercepts({
	@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,RowBounds.class, ResultHandler.class }),
	@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class }), 
})
public class PageInterceptor implements Interceptor {
	private static Map<String, MappedStatement> defaultMappedStatement = new ConcurrentHashMap<>();

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		System.out.println("PageInterceptor -- intercept");
		Executor executor = (Executor) invocation.getTarget();
		Object[] args = invocation.getArgs();
		MappedStatement mappedStatement = (MappedStatement) args[0];
		Object parameter = args[1];
		RowBounds rowBounds = (RowBounds) args[2];
		ResultHandler<?> resultHandler = (ResultHandler<?>) args[3];
		CacheKey cacheKey = null;
		BoundSql boundSql = null;
		if (args.length == 4) {
			boundSql = mappedStatement.getBoundSql(parameter);
			cacheKey = executor.createCacheKey(mappedStatement, parameter, rowBounds, boundSql);
		} else {
			cacheKey = (CacheKey) args[4];
			boundSql = (BoundSql) args[5];
		}
		if (!PageHelper.hasPagination()) {
			List<?> resultList = executor.query(mappedStatement, parameter, rowBounds, resultHandler, cacheKey,
					boundSql);
			return resultList;
		}
		Configuration configuration = mappedStatement.getConfiguration();
		String mappedStatementId = mappedStatement.getId();
		SqlSource sqlSource = mappedStatement.getSqlSource();
		List<ResultMap> resultMaps = mappedStatement.getResultMaps();
		ParameterMap parameterMap = mappedStatement.getParameterMap();
		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
		String origSql = boundSql.getSql();
		String countMappedStatementId = mappedStatementId + "_count";
		String countResultMaptId = mappedStatementId + "_count_result";
		MappedStatement countMappedStatement = defaultMappedStatement.get(countMappedStatementId);
		if (countMappedStatement == null) {
			List<ResultMap> countResultMaps = MybatisBuilder.resultMap(configuration, countResultMaptId, Integer.class,
					null);
			countMappedStatement = MybatisBuilder.mappedStatement(configuration, countMappedStatementId, sqlSource,
					SqlCommandType.SELECT, parameterMap, countResultMaps);
			defaultMappedStatement.put(countMappedStatementId, countMappedStatement);
		}

		BoundSql countBoundSql = new BoundSql(configuration, concatCountSql(origSql), parameterMappings, parameter);
		CacheKey countCacheKey = executor.createCacheKey(countMappedStatement, parameter, rowBounds, countBoundSql);
		countCacheKey.update("_count");
		List<Integer> countList = executor.query(countMappedStatement, parameter, rowBounds, resultHandler,
				countCacheKey, countBoundSql);
		PageInfo pageInfo = PageHelper.getPageInfo();
		pageInfo.setTotal(countList.get(0));

		int pageNum = pageInfo.getPageNum();
		int pageSize = pageInfo.getPageSize();
		int offset = (pageNum - 1) * pageSize;
		String pageSql = concatPageSql(origSql);
		String pageMappedStatementId = mappedStatementId + "_page";
		MappedStatement pageMappedStatement = defaultMappedStatement.get(pageMappedStatementId);
		if (pageMappedStatement == null) {
			pageMappedStatement = MybatisBuilder.mappedStatement(configuration, pageMappedStatementId, sqlSource,
					SqlCommandType.SELECT, parameterMap, resultMaps);
			defaultMappedStatement.put(pageMappedStatementId, pageMappedStatement);
		}
		List<ParameterMapping> pageParameterMappings = new ArrayList<>();
		pageParameterMappings.addAll(parameterMappings);
		BoundSql pageBoundSql = new BoundSql(configuration, pageSql, pageParameterMappings, parameter);
		ParameterMapping offsetParameterMapping = new ParameterMapping.Builder(configuration, "offset", Integer.class)
				.build();
		pageBoundSql.getParameterMappings().add(offsetParameterMapping);
		ParameterMapping limitParameterMapping = new ParameterMapping.Builder(configuration, "limit", Integer.class)
				.build();
		pageBoundSql.getParameterMappings().add(limitParameterMapping);
		pageBoundSql.setAdditionalParameter("offset", offset);
		pageBoundSql.setAdditionalParameter("limit", pageSize);
		CacheKey pageCacheKey = executor.createCacheKey(pageMappedStatement, parameter, rowBounds, pageBoundSql);
		pageCacheKey.update("_page");
		List<?> resultList = executor.query(pageMappedStatement, parameter, rowBounds, resultHandler, pageCacheKey,
				pageBoundSql);
		return resultList;

	}

	/**
	 */
	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	@Override
	public void setProperties(Properties properties) {

	}

	public static String concatCountSql(String sql) {
		StringBuffer sb = new StringBuffer("select count(1) from ");
		sb.append("(");
		sb.append(sql);
		sb.append(")");
		sb.append(" auto_count");
		return sb.toString();
	}

	public String concatPageSql(String sql) {
		StringBuffer sb = new StringBuffer();
		sb.append(sql).append(" limit ").append("?").append(",").append("?");
		return sb.toString();
	}

}
